﻿#include "jlogger.h"
#include <QMutexLocker>
#include <QFile>
#include <QDir>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
#include <QDateTime>
#include <QDate>
#include <QApplication>
#include <QTimer>
#include <QStringList>


#define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define YEAR qPrintable(QDate::currentDate().toString("yyyy"))
#define YEAR_MONTH qPrintable(QDate::currentDate().toString("yyyy-MM"))

//日志重定向
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const char *msg)
#else
void Log(QtMsgType type, const QMessageLogContext &info, const QString &msg)
#endif
{
    //加锁,防止多线程中qdebug太频繁导致崩溃
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    QString content;

    //这里可以根据不同的类型加上不同的头部用于区分
    switch (type) {
    case QtDebugMsg:
        content = QString("[%1][%2][line:%3][%4]: %5\n")
                .arg(info.file)
                .arg(info.function)
                .arg(info.line)
                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                .arg(msg);
        break;

    case QtWarningMsg:
        content = QString("[%1][%2][line:%3][%4]: %5\n")
                .arg(info.file)
                .arg(info.function)
                .arg(info.line)
                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                .arg(msg);
        break;

    case QtCriticalMsg:
        content = QString("[%1][%2][line:%3][%4]: %5\n")
                .arg(info.file)
                .arg(info.function)
                .arg(info.line)
                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                .arg(msg);
        break;

    case QtFatalMsg:
        content = QString("[%1][%2][line:%3][%4]: %5\n")
                .arg(info.file)
                .arg(info.function)
                .arg(info.line)
                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                .arg(msg);
        break;
    case QtInfoMsg:
        content = QString("[%1][%2][line:%3][%4]: %5\n")
                .arg(info.file)
                .arg(info.function)
                .arg(info.line)
                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                .arg(msg);
        break;
    }

    JLogger::Instance()->save(content, type);
}

QString logType(QtMsgType type)
{
    QString str;
    switch (type) {
    case QtDebugMsg:
        str = "debug";
        break;
    case QtWarningMsg:
        str = "warning";
        break;
    case QtCriticalMsg:
        str = "critical";
        break;
    case QtFatalMsg:
        str = "fatal";
        break;
    case QtInfoMsg:
        str = "info";
        break;
    }
    return str;
}

QScopedPointer<JLogger> JLogger::self;
JLogger *JLogger::Instance()
{
    if (self.isNull()) {
        static QMutex mutex;
        QMutexLocker locker(&mutex);
        if (self.isNull()) {
            self.reset(new JLogger);
        }
    }

    return self.data();
}

JLogger::JLogger(QObject *parent) : QObject(parent)
{
    //必须用信号槽形式,不然提示 QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
    //估计日志钩子可能单独开了线程
    connect(this, SIGNAL(send(QString)), SendLog::Instance(), SLOT(send(QString)));

    file = new QFile(this);
    toNet = false;
    //默认取应用程序根目录
    path = qApp->applicationDirPath();
    //默认取应用程序可执行文件名称
//    QString str = qApp->applicationFilePath();
//    QStringList list = str.split("/");
//    name = list.at(list.count() - 1).split(".").at(0);
    fileName = "";
}

JLogger::~JLogger()
{
    file->close();
}

//安装日志钩子,输出调试信息到文件,便于调试
void JLogger::start()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
    qInstallMsgHandler(Log);
#else
    qInstallMessageHandler(Log);
#endif
}

//卸载日志钩子
void JLogger::stop()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
    qInstallMsgHandler(0);
#else
    qInstallMessageHandler(nullptr);
#endif
}

void JLogger::save(const QString &content, QtMsgType type)
{
    //如果重定向输出到网络则通过网络发出去,否则输出到日志文件
    if (toNet) {
        emit send(content);
    } else {
        //方法改进:之前每次输出日志都打开文件,改成只有当日期改变时才新建和打开文件
        QString temp = QString("%1/log/%2/%3/").arg(path).arg(YEAR).arg(YEAR_MONTH);
        QDir dir;
        if(!dir.exists(temp)) {
            dir.mkpath(temp);
        }
        QString fileName = QString("%1/%2_%3.txt")
                .arg(temp)
                .arg(QDATE)
                .arg(logType(type));
        if (this->fileName != fileName) {
            this->fileName = fileName;
            if (file->isOpen()) {
                file->close();
            }

            file->setFileName(fileName);
//            file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
        }

        if(file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text)) {
            QTextStream logStream(file);
            logStream << content << "\n";
            file->close();
        }
    }
}

void JLogger::setToNet(bool toNet)
{
    this->toNet = toNet;
}

void JLogger::setPath(const QString &path)
{
    this->path = path;
}

void JLogger::setName(const QString &name)
{
    this->name = name;
}






//网络发送日志数据类
QScopedPointer<SendLog> SendLog::self;
SendLog *SendLog::Instance()
{
    if (self.isNull()) {
        static QMutex mutex;
        QMutexLocker locker(&mutex);
        if (self.isNull()) {
            self.reset(new SendLog);
        }
    }

    return self.data();
}

SendLog::SendLog(QObject *parent)
{
    Q_UNUSED(parent)
    socket = nullptr;
    server = new QTcpServer(this);
    connect(server, SIGNAL(newConnection()), this, SLOT(newConnection()));

    int listenPort = 6000;
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
    server->listen(QHostAddress::AnyIPv4, listenPort);
#else
    server->listen(QHostAddress::Any, listenPort);
#endif
}

SendLog::~SendLog()
{
    if (socket != nullptr) {
        socket->disconnectFromHost();
    }

    server->close();
}

void SendLog::newConnection()
{
    while (server->hasPendingConnections()) {
        socket = server->nextPendingConnection();
    }
}

void SendLog::send(const QString &content)
{
    if (socket != nullptr && socket->isOpen()) {
        socket->write(content.toUtf8());
        socket->flush();
    }
}
